package com.example.service.impl;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.entity.dto.*;
import com.example.entity.vo.request.AddCommentVO;
import com.example.entity.vo.request.TopicCreateVO;
import com.example.entity.vo.request.TopicUpdateVO;
import com.example.entity.vo.response.CommentVO;
import com.example.entity.vo.response.TopicDetailsVO;
import com.example.entity.vo.response.TopicPreviewVO;
import com.example.entity.vo.response.TopicTopVO;
import com.example.mapper.*;
import com.example.service.NotificationService;
import com.example.service.TopicService;
import com.example.utils.CacheUtils;
import com.example.utils.Const;
import com.example.utils.FlowUtils;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.xml.stream.events.Comment;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * 作者:xuSen
 * 日期2025/4/28 14:02
 */
@Service
public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements TopicService {
    // 使用Resource注解注入TopicTypeMapper对象，用于执行与话题类型相关的数据库操作
    @Resource
    private TopicTypeMapper topicTypeMapper;
    // 定义一个集合用于存储话题类型的ID
    private Set<Integer> type = null;
    // 使用PostConstruct注解标记init方法，表示在依赖注入完成后执行该方法进行初始化
    @PostConstruct
    private void init() {
        // 初始化type集合，通过调用listTypes方法获取所有话题类型，并将它们的ID收集到Set集合中
        type = this.listTypes().stream().map(TopicType::getId).collect(Collectors.toSet());
    }
    // 以下代码行被注释掉，未使用
    //    private final Set<Integer> type=this.listTypes().stream().map(TopicType::getId).collect(Collectors.toSet());
    // 使用Resource注解注入FlowUtils对象，用于执行与流程相关的操作
    @Resource
    private FlowUtils flowUtils;
    // 使用Resource注解注入CacheUtils对象，用于执行缓存操作
    @Resource
    private CacheUtils cacheUtils;
    // 使用Resource注解注入AccountMapper对象，用于执行与用户账户相关的数据库操作
    @Resource
    private AccountMapper accountMapper;
    // 使用Resource注解注入AccountDetailsMapper对象，用于执行与用户详细信息相关的数据库操作
    @Resource
    private AccountDetailsMapper accountDetailsMapper;
    // 使用Resource注解注入AccountPrivacyMapper对象，用于执行与用户隐私设置相关的数据库操作
    @Resource
    private AccountPrivacyMapper accountPrivacyMapper;
    // 使用Resource注解注入StringRedisTemplate对象，用于执行Redis字符串类型数据的操作
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    // 使用Resource注解注入TopicCommentMapper对象，用于执行与话题评论相关的数据库操作
    @Resource
    private TopicCommentMapper topicCommentMapper;
    // 使用Resource注解注入NotificationService对象，用于执行通知相关的服务操作
    @Resource
    private NotificationService notificationService;

    /**
     * 获取主题类型列表
     * 此方法用于从数据库中获取所有主题类型它没有输入参数，返回一个主题类型列表
     *
     * @return List<TopicType> 返回一个包含所有主题类型的列表
     */
    @Override
    public List<TopicType> listTypes() {
        return topicTypeMapper.selectList(null);
    }

    /**
     * 创建新主题
     * <p>
     * 此方法接收一个TopicCreateVO对象和一个用户ID作为参数，验证内容长度、主题类型，并检查用户发帖频率，
     * 以确保用户不超过发帖限制然后将用户输入的内容转换为JSON字符串，创建一个Topic对象，设置必要的属性，
     * 并尝试保存该主题如果保存成功，它将清除缓存并返回null；如果保存失败，则返回错误消息
     *
     * @param vo  TopicCreateVO对象，包含用户提交的主题信息
     * @param uid 用户ID，用于标识发帖用户
     * @return 如果内容过长或主题类型不存在，则返回相应的错误消息如果发帖太频繁，也返回相应的错误消息
     * 如果主题保存成功，返回null，否则返回内部错误消息
     */
    @Override
    public String createTopic(TopicCreateVO vo, int uid) {
        // 检查内容长度，如果过长则返回错误消息
        if (!textLimitCheck(vo.getContent(), 20000))
            return "内容过长,发文失败！";
        // 检查主题类型是否存在，如果不存在则返回错误消息
        if (!type.contains(vo.getType()))
            return "主题类型不存在,发文失败！";
        // 构造缓存键，用于限制用户发帖频率
        String key = Const.FORUM_TOPIC_CRETE_COUNTER + uid;
        // 检查用户是否发帖太频繁，如果是则返回错误消息
        if (!flowUtils.limitPeriodCounterCheck(key, 3, 3600)) {
            return "您发帖太频繁了，稍后再试！";
        }
        // 创建一个新的Topic对象
        Topic topic = new Topic();
        // 将用户提交的信息复制到Topic对象中
        BeanUtils.copyProperties(vo, topic);
        // 将内容转换为JSON字符串并设置到Topic对象中
        topic.setContent(vo.getContent().toJSONString());
        // 设置发帖用户的ID
        topic.setUid(uid);
        // 设置发帖时间
        // 设置时间
        topic.setTime(new Date());
        cacheUtils.deleteCachePattern(Const.FORUM_TOPIC_PREVIEW_CACHE + "*");
        // 尝试保存Topic对象，如果成功则清除缓存并返回null，如果失败则返回错误消息
        //
        if (save(topic)) {

            cacheUtils.deleteCachePattern(Const.FORUM_TOPIC_PREVIEW_CACHE + "*");
            return null;
        } else {
            return "内部错误，请联系管理员！";
        }
    }

    @Override
    public String updateTopic(int uid, TopicUpdateVO vo) {
        // 检查内容长度，如果过长则返回错误消息
        if (!textLimitCheck(vo.getContent(), 20000))
            return "修改内容过长,发文失败！";
        // 检查主题类型是否存在，如果不存在则返回错误消息
        if (!type.contains(vo.getType()))
            return "主题类型不存在,发文失败！";
        baseMapper.update(null, Wrappers.<Topic>update()
                .eq("uid", uid)
                .eq("id", vo.getId())
                .set("title", vo.getTitle())
                .set("content", vo.getContent().toString())
                .set("type", vo.getType()));
        return null;
    }

    /**
     * 添加评论方法
     *
     * @param addCommentVO 包含评论信息的视图对象
     * @param uid 用户ID
     * @return 如果发表评论失败，返回错误消息；否则返回null
     */
    @Override
    public String addComment(AddCommentVO addCommentVO, int uid) {
        // 检查内容长度，如果过长则返回错误消息
        if (!textLimitCheck(JSONObject.parseObject(addCommentVO.getContent()), 2000))
            return "评论内容太多，发表失败";

        // 使用缓存和计数器限制用户发表评论的频率
        String key = Const.FORUM_TOPIC_COMMENT_COUNTER + uid;
        if (!flowUtils.limitPeriodCounterCheck(key, 5, 60)) {
            return "您发表评论太频繁了，稍后再试！";
        }

        // 创建评论对象并设置基本属性
        TopicComment topicComment = new TopicComment();
        topicComment.setUid(uid);
        BeanUtils.copyProperties(addCommentVO, topicComment);
        topicComment.setTime(new Date());

        // 插入评论到数据库
        topicCommentMapper.insert(topicComment);

        // 获取话题和账户信息
        Topic topic = baseMapper.selectById(addCommentVO.getTid());
        Account account = accountMapper.selectById(uid);

        // 处理回复评论的情况，发送通知给被回复的用户
        if (addCommentVO.getQuote() > 0) {
            TopicComment quoteComment = topicCommentMapper.selectById(addCommentVO.getQuote());
            if (!Objects.equals(account.getId(), quoteComment.getUid())) {
                notificationService.addNotification(quoteComment.getUid(), "您有新的帖子被回复",
                        account.getUsername()+"回复了你的帖子，快去看看吧",
                        "success", "/index/topic-detail/"+quoteComment.getTid());
            }
        // 处理直接回复话题的情况，发送通知给话题的发布者
        } else if (!Objects.equals(account.getId(), topic.getUid())) {
            notificationService.addNotification(topic.getUid(), "您有新的帖子被回复",
                    account.getUsername()+"回复了你的帖子"+topic.getTitle()+"，快去看看吧",
                    "success", "/index/topic-detail/"+topic.getId());
        }

        // 如果执行成功，返回null
        return null;
    }

    /**
     * 根据主题ID和页码获取评论列表
     *
     * @param tid        主题ID，用于查询对应的评论
     * @param pageNumber 页码，用于分页查询评论
     * @return 返回一个CommentVO对象列表，每个对象包含评论信息
     */
    @Override
    public List<CommentVO> getComments(int tid, int pageNumber) {
        Page<TopicComment> page = Page.of(pageNumber, 10);// 创建一个分页对象，每页显示10条评论
        topicCommentMapper.selectPage(page, Wrappers.<TopicComment>query().eq("tid", tid));// 查询数据库中符合主题ID的评论，并进行分页
        return page.getRecords().stream().map(dto -> { // 将查询到的评论数据转换为CommentVO对象列表
            CommentVO commentVO = new CommentVO(); // 创建一个CommentVO对象，用于存储转换后的评论信息
            BeanUtils.copyProperties(dto, commentVO);// 将TopicComment对象的属性复制到CommentVO对象中
            if (dto.getQuote() > 0) {// 如果评论中有引用其他评论的内容，则查询并处理引用内容
                TopicComment content = topicCommentMapper.selectOne(Wrappers.<TopicComment>query().eq("id", dto.getQuote()).orderByAsc("time"));
                if (content != null) {
                    JSONObject object = JSONObject.parseObject(content.getContent()); // 查询引用的评论内容，并解析为JSONObject对象
                    StringBuilder stringBuilder = new StringBuilder();// 创建一个StringBuilder对象，用于存储处理后的引用内容
                    this.shortContent(object.getJSONArray("ops"), stringBuilder, (ignore -> {
                    }));// 调用shortContent方法处理引用内容，并将结果追加到StringBuilder对象中
                    commentVO.setQuote(stringBuilder.toString());// 将处理后的引用内容设置到CommentVO对象中
                } else {
                    commentVO.setQuote("引用的评论已被删除");
                }

            }
            // 创建一个CommentVO.User对象，用于存储评论用户的详细信息
            CommentVO.User user = new CommentVO.User();
            // 调用fillUserDetailsByPrivacy方法填充用户详细信息
            this.fillUserDetailsByPrivacy(user, dto.getUid());
            // 将用户信息设置到CommentVO对象中
            commentVO.setUser(user);
            // 返回转换后的CommentVO对象
            return commentVO;
        }).toList();
    }

    /**
     * 删除评论
     * 根据评论ID和用户ID来删除指定的评论
     * 这里使用了MyBatis-Plus的封装方法，简化了删除操作
     *
     * @param cid 评论的ID，用于定位要删除的评论
     * @param uid 用户的ID，用于确认删除操作的执行者
     */
    @Override
    public void deleteComment(int cid, int uid) {
        // 执行删除操作，条件是评论ID和用户ID匹配
        topicCommentMapper.delete(Wrappers.<TopicComment>query().eq("id", cid).eq("uid", uid));

    }

    /**
     * 删除主题
     * 根据主题ID和用户ID来删除对应的主题此方法确保只有主题的创建者才能删除主题，
     * 通过比较主题的ID和用户的ID来定位并删除主题
     *
     * @param tid 主题ID，用于标识特定的主题
     * @param uid 用户ID，用于确认删除操作的用户身份
     */
    @Override
    public void deleteTopic(int tid, int uid) {
        // 使用Wrapper构建查询条件，确保删除操作只针对指定的主题和用户
        remove(Wrappers.<Topic>query().eq("id", tid).eq("uid", uid));
        cacheUtils.deleteCachePattern(Const.FORUM_TOPIC_PREVIEW_CACHE + "*");
    }

    /**
     * 根据页面和类型获取主题预览列表
     * 首先尝试从缓存中获取数据，如果缓存中没有则从数据库中查询，并将结果缓存起来
     *
     * @param pageNumber 页面号，用于分页
     * @param type       主题类型，用于筛选主题
     * @return 主题预览列表，如果列表为空则返回null
     */
    @Override
    public List<TopicPreviewVO> listTopicByPage(int pageNumber, int type) {
        // 构造缓存键值
        String key = Const.FORUM_TOPIC_PREVIEW_CACHE + pageNumber + ":" + type;
        // 尝试从缓存中获取主题预览列表
        List<TopicPreviewVO> topicPreviewVOS = cacheUtils.takeListFromCache(key, TopicPreviewVO.class);
        // 如果缓存中存在主题预览列表，则直接返回
        if (topicPreviewVOS != null)
            return topicPreviewVOS;
        // 初始化主题列表
//        List<Topic> topics;
        Page<Topic> page = Page.of(pageNumber, 10);
        // 根据类型查询主题列表
        if (type == 0) {
            // 如果类型为0，查询所有主题
            baseMapper.selectPage(page, Wrappers.<Topic>query().orderByDesc("time"));
        } else {
            // 否则，根据类型查询主题列表
            baseMapper.selectPage(page, Wrappers.<Topic>query().eq("type", type).orderByDesc("time"));
        }
        List<Topic> topics = page.getRecords();
        // 如果主题列表为空，则返回null
        if (topics.isEmpty()) return null;
        // 将主题列表转换为主题预览列表
        topicPreviewVOS = topics.stream().map(this::resolveTopicPreviewVO).toList();
        // 将主题预览列表保存到缓存中
        cacheUtils.saveListToCache(key, topicPreviewVOS, 60);
        // 返回主题预览列表
        return topicPreviewVOS;
    }

    /**
     * 获取顶级主题列表
     *
     * 本方法通过查询数据库中标记为顶级（top字段为1）的主题，将其映射为TopicTopVO对象，并以列表形式返回
     *
     * @return 返回一个TopicTopVO对象列表，每个对象包含顶级主题的ID、标题和时间
     */
    @Override
    public List<TopicTopVO> listTopTopic() {
        // 查询数据库中所有标记为顶级的主题，仅选择id、title和time字段
        List<Topic> topics = baseMapper.selectList(Wrappers.<Topic>query().select("id", "title", "time").eq("top", 1));

        // 将查询到的主题映射为TopicTopVO对象，并收集到一个新的列表中
        return topics.stream().map(topic -> new TopicTopVO(topic.getId(), topic.getTitle(), topic.getTime())).toList();
    }

    /**
     * 根据主题ID获取主题详情
     *
     * @param Tid 主题ID，用于查询数据库中对应的主题信息//12
     * @return TopicDetailsVO 返回一个包含主题详情的对象，包括主题的基本信息和用户信息
     */
    @Override
    public TopicDetailsVO getTopic(int Tid, int uid) {//12
        // 初始化一个TopicDetailsVO对象，用于封装主题详情信息
        TopicDetailsVO topicDetailsVO = new TopicDetailsVO();
        // 通过MyBatis Plus的baseMapper根据主题ID查询数据库中的主题信息
        Topic topic = baseMapper.selectById(Tid);//uid   5
        // 使用Spring框架的BeanUtils工具类，将查询到的主题信息复制到TopicDetailsVO对象中
        BeanUtils.copyProperties(topic, topicDetailsVO);
        // 创建TopicDetailsVO.Interact对象，用于表示用户与主题的交互情况
        // 该对象初始化时需要两个参数：是否点赞和是否收藏
        // 这里通过调用hasInteract方法判断用户是否对当前主题进行了点赞和收藏操作
        TopicDetailsVO.Interact interact = new TopicDetailsVO.Interact(
                this.hasInteract(Tid, uid, "like"),
                this.hasInteract(Tid, uid, "collect"));
        // 将创建的Interact对象设置到topicDetailsVO中，以便在主题详情中展示交互信息
        topicDetailsVO.setInteract(interact);
        // 创建一个TopicDetailsVO.User对象，用于封装主题发布者的用户信息
        TopicDetailsVO.User user = new TopicDetailsVO.User();

        // 调用fillUserDetailsByPrivacy方法，根据主题信息中的用户ID填充用户详情，并设置到TopicDetailsVO对象中
        topicDetailsVO.setUser(this.fillUserDetailsByPrivacy(user, topic.getUid()));
        topicDetailsVO.setComments(topicCommentMapper.selectCount(Wrappers.<TopicComment>query().eq("tid", Tid)));
        // 返回填充好主题信息和用户信息的TopicDetailsVO对象
        return topicDetailsVO;
    }


    /**
     * 由于论坛交互数据(如点赞、收藏等)更新可能会非常频繁
     * 更新信息实时到mysql不太现实，所以需要用Redis做缓冲并在合适的时机一次性入库一段时间内的全部数据
     * 当数据更新到来时，会创建一个新的定时任务，此任务会在一段时间之后执行
     * 将全部Redis暂时缓存的信息一次性加入到数据库，从而缓解MySQL压力，如果
     * 在定时任务已经设定期间又有新的更新到来，仅更新Redis不创建新的延时任务
     *
     * @param interact
     * @param state
     */
    @Override
    public void interact(Interact interact, boolean state) {
        // 获取交互类型，如"like"、"favorite"等
        String type = interact.getType();
        // 使用intern确保类型字符串在字符串常量池中，以减少内存消耗
        synchronized (type.intern()) {
            // 将交互信息根据类型存入Redis中对应的hash表里
            stringRedisTemplate.opsForHash().put(type, interact.toKey(), Boolean.toString(state));
            this.saveInteractSchedule(type);
        }
    }

    /**
     * 重写列表查询方法以获取用户收藏的主题列表
     *
     * @param uid 用户ID，用于查询该用户收藏的所有主题
     * @return 返回一个TopicPreviewVO对象列表，每个对象代表用户收藏的一个主题预览
     */
    @Override
    public List<TopicPreviewVO> ListTopicCollects(int uid) {
        // 从数据库中获取用户收藏的主题列表
        // 使用Java Stream API对查询结果进行处理，将每个主题映射为一个TopicPreviewVO对象
        // 这里使用流是因为它提供了高效的处理集合数据的方法，使代码更加简洁和易于阅读
        return baseMapper.collectTopics(uid).stream().map(topic -> {
            // 创建一个新的TopicPreviewVO对象来存储主题预览信息
            TopicPreviewVO topicPreviewVO = new TopicPreviewVO();
            // 使用Spring框架的BeanUtils工具类来复制主题实体的属性到TopicPreviewVO对象
            // 这样做是为了避免手动编写属性复制的代码，提高开发效率并减少出错
            BeanUtils.copyProperties(topic, topicPreviewVO);
            // 将转换后的TopicPreviewVO对象收集到列表中
            return topicPreviewVO;
        }).toList();
    }


    /**
     * 检查用户是否对特定项进行过交互
     *
     * @param tid  项ID，表示交互的目标项
     * @param uid  用户ID，表示进行交互的用户
     * @param type 交互类型，如点赞、收藏等
     * @return 如果用户对项进行过交互，则返回true；否则返回false
     */
    private boolean hasInteract(int tid, int uid, String type) {//12   5    like
        // 构造Redis中哈希表的键
        String key = tid + ":" + uid;
        // 检查在指定的交互类型中，用户是否已经对项进行过交互
        if (stringRedisTemplate.opsForHash().hasKey(type, key)) {
            // 如果用户已经进行过交互，返回交互的状态
            return Boolean.parseBoolean(stringRedisTemplate.opsForHash().entries(type).get(key).toString());
        }
        // 如果在Redis中没有找到交互记录，调用数据库操作检查用户是否进行过交互
        return baseMapper.userInteractCount(tid, uid, type) > 0;
    }

    // 用于存储不同类型的交互状态，键为类型字符串，值为布尔值表示是否正在保存交互
    private final Map<String, Boolean> state = new HashMap<>();
    // 创建一个定时任务调度器，用于执行定时保存交互的任务
    ScheduledExecutorService service = Executors.newScheduledThreadPool(2);

    /**
     * 安排定时保存特定类型的交互
     * 此方法避免了同一时间对相同类型交互的重复保存
     *
     * @param type 交互类型，用于区分不同的交互
     */
    private void saveInteractSchedule(String type) {
        // 检查当前类型是否已经在保存中，如果没有则安排新的保存任务
        if (!state.getOrDefault(type, false)) {
            // 标记当前类型为正在保存，防止重复执行
            state.put(type, false);
            // 安排一个定时任务，在5秒后执行保存交互的操作
            service.schedule(() -> {
                // 调用保存交互的方法
                this.saveInteract(type);
                // 重置当前类型的保存状态，以便下一次执行
                state.put(type, false);
            }, 5, TimeUnit.SECONDS);
        }
    }

    /**
     * 保存交互数据
     * 根据交互类型从Redis中获取数据，并根据数据状态将其添加或删除
     * 此方法使用同步块来确保同一时间只有一个线程可以执行此方法，避免数据一致性问题
     *
     * @param type 交互类型，用于在Redis中查找相应的数据
     */
    private void saveInteract(String type) {
        // 使用String intern方法确保类型字符串在常量池中，以实现同步
        synchronized (type.intern()) {
            // 初始化两个列表，一个用于存储需要检查的交互，另一个用于存储不需要检查的交互
            List<Interact> check = new LinkedList<>();
            List<Interact> uncheck = new LinkedList<>();

            // 从Redis中获取所有指定类型的交互数据，并遍历每项数据
            stringRedisTemplate.opsForHash().entries(type).forEach((key, value) -> {
                // 根据数据值判断交互是否需要检查，并将其添加到相应的列表中
                if (Boolean.parseBoolean(value.toString()))
                    check.add(Interact.parsInteract(key.toString(), type));
                else
                    uncheck.add(Interact.parsInteract(key.toString(), type));
            });

            // 如果有需要检查的交互，则调用基础映射器的方法将其添加到数据库中
            if (!check.isEmpty()) {
                baseMapper.addInteract(check, type);
                System.out.println("已经点赞");
            }

            // 如果有不需要检查的交互，则调用基础映射器的方法将其从数据库中删除
            if (!uncheck.isEmpty()) {
                baseMapper.deleteInteract(uncheck, type);
                System.out.println("已经取消点赞");
            }

            // 最后，从Redis中删除所有指定类型的交互数据，以保持数据一致性
            stringRedisTemplate.delete(type);
        }
    }

    /**
     * 根据用户隐私设置填充用户详细信息
     * 此方法首先从数据库中获取用户的账户详情、账户信息和账户隐私设置
     * 然后根据账户隐私设置中隐藏的字段，在复制属性时进行忽略
     * 这样可以确保用户隐私字段不会被暴露或错误地填充到目标对象中
     *
     * @param target 目标对象，用于接收用户信息的填充，可以是任何类型，但通常是一个用户信息相关的对象
     * @param uid    用户ID，用于查询用户的相关信息
     * @param <T>    泛型参数，表示目标对象的类型，允许此方法适用于不同类型的对象
     * @return 返回填充后的目标对象，包含根据用户隐私设置过滤后的用户信息
     */
    private <T> T fillUserDetailsByPrivacy(T target, int uid) {
        // 查询用户账户详情
        AccountDetails accountDetails = accountDetailsMapper.selectById(uid);
        // 查询用户账户基本信息
        Account account = accountMapper.selectById(uid);
        // 查询用户账户隐私设置
        AccountPrivacy accountPrivacy = accountPrivacyMapper.selectById(uid);
        // 获取隐私设置中隐藏的字段列表
        String[] ignores = accountPrivacy.hiddenFields();

        // 根据隐藏字段列表，复制账户信息到目标对象，忽略隐藏字段
        BeanUtils.copyProperties(account, target, ignores);
        // 根据相同隐藏字段列表，复制账户详情到目标对象，同样忽略隐藏字段
        BeanUtils.copyProperties(accountDetails, target, ignores);
        // 返回填充后的目标对象
        return target;

    }

    /**
     * 将Topic对象转换为TopicPreviewVO对象
     * 此方法主要用于从Topic对象中提取足够的信息来生成一个预览版本的对象，包括文本预览和图片列表
     *
     * @param topic Topic对象，包含完整的主题内容和相关信息
     * @return 返回一个TopicPreviewVO对象，包含从Topic对象复制的属性，以及解析出的文本预览和图片列表
     */
    private TopicPreviewVO resolveTopicPreviewVO(Topic topic) {
        // 创建一个新的TopicPreviewVO对象
        TopicPreviewVO topicPreviewVO = new TopicPreviewVO();
        BeanUtils.copyProperties(accountMapper.selectById(topic.getUid()), topicPreviewVO);
        // 将Topic对象的属性复制到TopicPreviewVO对象中
        BeanUtils.copyProperties(topic, topicPreviewVO);
        topicPreviewVO.setLike(baseMapper.interactCount(topic.getId(), "like"));
        topicPreviewVO.setCollect(baseMapper.interactCount(topic.getId(), "collect"));
        // 初始化一个空的图片列表
        List<String> images = new ArrayList<>();
        // 初始化一个空的StringBuilder对象，用于构建文本预览
        StringBuilder previewText = new StringBuilder();
        // 解析Topic对象的内容，提取文本和图片
        JSONArray ops = JSONObject.parseObject(topic.getContent()).getJSONArray("ops");
        this.shortContent(ops, previewText, Object -> images.add(Object.toString()));
        // 设置文本预览，如果超过300字符则截取前300字符
        topicPreviewVO.setText(previewText.length() > 300 ? previewText.substring(0, 300) : previewText.toString());
        // 设置图片列表
        topicPreviewVO.setImages(images);
        return topicPreviewVO;
    }

    private void shortContent(JSONArray ops, StringBuilder previewText, Consumer<Object> imagesHandler) {
        for (Object op : ops) {
            // 获取当前操作的插入内容
            Object insert = JSONObject.from(op).get("insert");
            // 如果插入内容是字符串类型，则将其添加到文本预览中，直到达到300字符为止
            if (insert instanceof String text) {
                if (previewText.length() >= 300) continue;
                previewText.append(text);
            } else if (insert instanceof Map<?, ?> map) {
                // 如果插入内容是图片映射，则将其添加到图片列表中
                Optional.ofNullable(map.get("image"))
                        .ifPresent(imagesHandler);
            }
        }
    }

    /**
     * 检查文本长度是否超过限制
     * 该方法用于确保给定的JSONObject中的文本内容长度不超过20000个字符
     * 主要用于在进行进一步处理前，对输入数据进行有效性校验
     *
     * @param object 包含文本操作的JSONObject，不能为空
     * @return boolean 如果文本长度不超过20000个字符，则返回true；否则返回false
     */
    private boolean textLimitCheck(JSONObject object, int max) {
        // 检查输入的JSONObject是否为null
        if (object == null) return false;

        // 初始化文本长度计数器
        long length = 0;

        // 遍历JSONObject中的"ops"数组，累加每个操作中的文本长度
        for (Object ops : object.getJSONArray("ops")) {
            // 获取当前操作中插入的文本长度并累加
            length += JSONObject.from(ops).getString("insert").length();

            // 如果累加的文本长度超过20000个字符，则返回false
            if (length > max) return false;
        }

        // 如果所有操作的文本长度总和不超过20000个字符，则返回true
        return true;
    }
}
